package com.chuhe.starry.websocket;

import com.alibaba.fastjson.JSON;
import com.chuhe.common.constans.MyConstants;
import com.chuhe.common.domain.dto.MessageSendDto;
import com.chuhe.common.domain.dto.WsInitData;
import com.chuhe.common.domain.entity.*;
import com.chuhe.common.domain.enums.MessageTypeEnum;
import com.chuhe.common.domain.enums.UserContactTypeEnum;
import com.chuhe.common.domain.vo.resp.loadContactResp;
import com.chuhe.common.utils.JsonUtils;
import com.chuhe.common.utils.JsonUtils2;
import com.chuhe.common.utils.RedisComponet;
import com.chuhe.common.utils.StringTools;
import com.chuhe.starry.dao.*;
import com.chuhe.starry.mapper.ChatSessionUserMapper;
import com.chuhe.starry.mapper.UserContactMapper;
import com.chuhe.starry.service.adapter.UserContactAdapter;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;


/**
 * @Author: 楚河
 * @Date: 2025年05月19日21:39
 */
@Component
public class ChannelContextUtils {
    @Autowired
    private RedisComponet redisComponet;
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private ChatSessionUserDao chatSessionUserDao;
    @Autowired
    private ChatSessionUserMapper chatSessionUserMapper;
    @Autowired
    private UserContactDao userContactDao;
    @Autowired
    private UserContactMapper userContactMapper;
    @Autowired
    private ChatMessageDao chatMessageDao;
    @Autowired
    private UserContactApplyDao userContactApplyDao;

    /**
     * 用户Channle缓存
     * @Author: 楚河
     * @Date: 2025-05-19 22:09
     */
    public static final ConcurrentMap<String, Channel> USER_CONTEXT_MAP = new ConcurrentHashMap();

    public static final ConcurrentMap<String, ChannelGroup> GROUP_CONTEXT_MAP = new ConcurrentHashMap();

    /**
     * 将UserId绑定到Channle
     * @Author: 楚河
     * @Date: 2025-05-19 22:10
     */
    public void addContext(String userId, Channel channel){
        //将用户Id绑定到channel...
        String channelId = channel.id().toString();
        AttributeKey attributeKey = null;
        if(!AttributeKey.exists(channelId)){
            attributeKey = AttributeKey.newInstance(channelId);
        }else {
            attributeKey = AttributeKey.valueOf(channelId);
        }
        channel.attr(attributeKey).set(userId);

        //查询该用户下所有联系人 将群组内联系人关联...
        List<String> contactIdList = redisComponet.getUserContactIdList(userId);
        for (String groupId : contactIdList) {
            if(groupId.startsWith(UserContactTypeEnum.GROUP.getPrefix())){
                add2Group(groupId,channel);
            }
        }

        USER_CONTEXT_MAP.put(userId,channel);//绑定通道
        redisComponet.saveUserHeartBeat(userId); //客户端连接就开始发送心跳

        //更新用户最后连接时间...
        userInfoDao.updateLastLoginTimeByUserId(userId);
        //给用户发送消息
        UserInfo userInfo = userInfoDao.selectByUserId(userId);
        Long sourceLastOffTime = userInfo.getLastOffTime();
        //这里避免毫秒时间差，所以减去1秒的时间
        //如果时间太久，只取最近三天的消息数
        Long lastOffTime = sourceLastOffTime;
        if (sourceLastOffTime != null && System.currentTimeMillis() - MyConstants.MILLISECOND_3DAYS_AGO > sourceLastOffTime) {
            lastOffTime = System.currentTimeMillis() - MyConstants.MILLISECOND_3DAYS_AGO;
        }else {
            lastOffTime = MyConstants.LENGTH_0.longValue();
        }

        /**
         * 1、查询会话信息 查询用户所有会话列表，避免换设备会话不同步
         */
        List<ChatSessionUser> chatSessionUserList = chatSessionUserMapper.selectSessionUserListVo(userId);
        WsInitData wsInitData = new WsInitData();
        wsInitData.setChatSessionList(chatSessionUserList);

        /**
         * 2、查询群组聊天消息
         */
        //查询群组联系人
        UserContact userContact = UserContactAdapter.buildFindUserOrGroup(userId, UserContactTypeEnum.GROUP.getType());
        List<loadContactResp> groupContactList = userContactMapper.findUserOrGroup(userContact);
        List<String> groupIdList = groupContactList.stream()
                .map(item -> item.getContactId())
                .collect(Collectors.toList());
        //将自己也添加进去
        groupIdList.add(userId);

        //查询消息
        List<ChatMessage> chatMessageList = chatMessageDao.selectAllMessage(groupIdList,lastOffTime);
        wsInitData.setChatMessageList(chatMessageList);
        /**
         * 3、查询好友申请数量
         */
        Integer count = userContactApplyDao.selectCount(userId);
        wsInitData.setApplyCount(count);

        //发送消息...
        MessageSendDto messageSendDto = new MessageSendDto();
        messageSendDto.setMessageType(MessageTypeEnum.INIT.getType());
        messageSendDto.setContactId(userId);
        messageSendDto.setExtendData(wsInitData);

        sendMsg(messageSendDto, userId);
    }

    /**
     * 发送消息
     * 在即时通讯场景中，当新消息数据入库后，
     * 服务端会通过 WebSocket（WS）将该数据直接推送给相关客户端，
     * 客户端无需主动请求或刷新页面，即可实时渲染新内容。
     * @Author: 楚河
     * @Date: 2025-05-21 18:36
     */
    public void sendMsg(MessageSendDto messageSendDto, String reciveId) {
        if (reciveId == null) {
            return;
        }
        Channel sendChannel = USER_CONTEXT_MAP.get(reciveId);
        if (sendChannel == null) {
            return;
        }
        //相当于客户而言，联系人就是发送人，所以这里转换一下再发送,好友打招呼信息发送给自己需要特殊处理
        if (MessageTypeEnum.ADD_FRIEND_SELF.getType().equals(messageSendDto.getMessageType())) {
            // 处理"添加好友(自己)"类型的消息
            UserInfo userInfo = (UserInfo) messageSendDto.getExtendData();
            messageSendDto.setMessageType(MessageTypeEnum.ADD_FRIEND.getType());
            messageSendDto.setContactId(userInfo.getUserId());
            messageSendDto.setContactName(userInfo.getNickName());
            messageSendDto.setExtendData(null);
        } else {
            // 处理普通消息
            messageSendDto.setContactId(messageSendDto.getSendUserId());
            messageSendDto.setContactName(messageSendDto.getSendUserNickName());
        }
        sendChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils2.convertObj2Json(messageSendDto)));
    }

    /**
     * 群组
     * @Author: 楚河
     * @Date: 2025-05-19 22:14
     */
    private void add2Group(String groupId, Channel channel) {//用户加入的群组id和通道
        ChannelGroup group = GROUP_CONTEXT_MAP.get(groupId);
        if(group == null){
            group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            GROUP_CONTEXT_MAP.put(groupId,group);
        }
        if(channel == null){
            return;
        }
        group.add(channel);
    }

    /**
     * 用户下线
     * @Author: 楚河
     * @Date: 2025-05-21 16:55
     */
    public void removeContext(Channel channel){
        //删除通道连接缓存...
        Attribute<String> attribute = channel.attr(AttributeKey.valueOf(channel.id().toString()));
        String userId = attribute.get();
        if(!StringTools.isEmpty(userId)){
            USER_CONTEXT_MAP.remove(userId);
        }
        //删除Redis心跳缓存...
        redisComponet.removeUserHeartBeat(userId);
        //更新最新离线时间...
        userInfoDao.updateLastOffTimeByUserId(userId);
    }

    /**
     * 消息处理器
     * @Author: 楚河
     * @Date: 2025-05-22 16:09
     */
    public void sendMessage(MessageSendDto messageSendDto){
        UserContactTypeEnum contactTypeEnum = UserContactTypeEnum.getByPrefix(messageSendDto.getContactId());
        switch (contactTypeEnum){
            case USER:
                send2User(messageSendDto);
                break;
             case GROUP:
                 sendMsg2Group(messageSendDto);
        }
    }
    /**
     * 发送群组消息
     * @Author: 楚河
     * @Date: 2025-05-22 16:12
     */
    private void sendMsg2Group(MessageSendDto messageSendDto) {
        if (messageSendDto.getContactId() == null) {
            return;
        }
        //获取群组通道
        ChannelGroup group = GROUP_CONTEXT_MAP.get(messageSendDto.getContactId());
        if (group == null) {
            return;
        }
        //发送消息
        group.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(messageSendDto)));

        //移除群聊
        MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByType(messageSendDto.getMessageType());
        if (MessageTypeEnum.LEAVE_GROUP == messageTypeEnum || MessageTypeEnum.REMOVE_GROUP == messageTypeEnum) {
            String userId = (String) messageSendDto.getExtendData();
            redisComponet.removeUserContact(userId, messageSendDto.getContactId());
            Channel channel = USER_CONTEXT_MAP.get(userId);
            if (channel == null) {
                return;
            }
            group.remove(channel);
        }

        if (MessageTypeEnum.DISSOLUTION_GROUP == messageTypeEnum) {
            GROUP_CONTEXT_MAP.remove(messageSendDto.getContactId());
            group.close();
        }
    }

    /**
     * 发送单聊消息
     * @Author: 楚河
     * @Date: 2025-05-22 16:12
     */
    private void send2User(MessageSendDto messageSendDto) {
        String contactId = messageSendDto.getContactId();
        sendMsg(messageSendDto,contactId);
        //强制下线
        if(MessageTypeEnum.FORCE_OFF_LINE.getType().equals(messageSendDto.getContactType())){
            closeContext(contactId);
        }
    }
    /**
     * 用户下线
     * @Author: 楚河
     * @Date: 2025-05-22 16:14
     */
    public void closeContext(String userId) {
        if (StringTools.isEmpty(userId)) {
            return;
        }
        redisComponet.cleanUserTokenByUserId(userId);
        Channel channel = USER_CONTEXT_MAP.get(userId);
        USER_CONTEXT_MAP.remove(userId);
        if (channel != null) {
            channel.close();
        }
    }

    /**
     * 将联系人添加至群组
     * @Author: 楚河
     * @Date: 2025-05-23 11:32
     */
    public void addUser2Group(String userId, String groupId) {
        Channel channel = USER_CONTEXT_MAP.get(userId);
        add2Group(groupId, channel);
    }
}
